/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.client.gui.sound;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.freecolandroid.repackaged.javax.sound.sampled.AudioFormat;
import org.freecolandroid.repackaged.javax.sound.sampled.AudioInputStream;
import org.freecolandroid.repackaged.javax.sound.sampled.AudioSystem;
import org.freecolandroid.repackaged.javax.sound.sampled.DataLine;
import org.freecolandroid.repackaged.javax.sound.sampled.FloatControl;
import org.freecolandroid.repackaged.javax.sound.sampled.Mixer;
import org.freecolandroid.repackaged.javax.sound.sampled.SourceDataLine;
import net.sf.freecol.FreeCol;
import net.sf.freecol.common.option.AudioMixerOption;
import net.sf.freecol.common.option.AudioMixerOption.MixerWrapper;
import net.sf.freecol.common.option.PercentageOption;
/**
* Stripped down class for playing sound.
*/
public class SoundPlayer {
private static Logger logger = Logger.getLogger(SoundPlayer.class.getName());
private Mixer mixer;
private int volume;
private SoundPlayerThread soundPlayerThread;
/**
* Creates a sound player.
*
* @param mixerOption The option for setting the mixer to use.
* @param volumeOption The volume option to use when playing audio.
*/
public SoundPlayer(AudioMixerOption mixerOption,
PercentageOption volumeOption) {
setMixer(mixerOption.getValue());
if (mixer == null) {
throw new IllegalStateException("Mixer unavailable.");
}
mixerOption.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
setMixer((MixerWrapper) e.getNewValue());
}
});
setVolume(volumeOption.getValue());
volumeOption.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
setVolume((Integer) e.getNewValue());
}
});
soundPlayerThread = new SoundPlayerThread();
soundPlayerThread.start();
}
/**
* Gets the mixer.
*
* @return The current mixer.
*/
public Mixer getMixer () {
return mixer;
}
private void setMixer(MixerWrapper mw) {
try {
mixer = AudioSystem.getMixer(mw.getMixerInfo());
} catch (Exception e) {
logger.log(Level.WARNING, "Could not set mixer", e);
mixer = null;
}
}
/**
* Gets the volume.
*
* @return The current volume.
*/
public int getVolume() {
return volume;
}
private void setVolume(int volume) {
this.volume = volume;
}
/**
* Plays a file once.
*
* @param file The <code>File</code> to be played.
*/
public void playOnce(File file) {
if (getMixer() == null) return; // Fail faster.
soundPlayerThread.add(file);
soundPlayerThread.awaken();
}
/**
* Stops the current sound.
*/
public void stop() {
soundPlayerThread.stopPlaying();
soundPlayerThread.awaken();
}
/**
* Thread for playing sound files.
*/
private class SoundPlayerThread extends Thread {
private final List<File> playList = new ArrayList<File>();
private boolean playDone = true;
public SoundPlayerThread() {
super(FreeCol.CLIENT_THREAD + "SoundPlayer");
}
private synchronized void awaken() {
this.notify();
}
private synchronized void goToSleep() throws InterruptedException {
this.wait();
}
public synchronized boolean keepPlaying() {
return !playDone;
}
public synchronized void startPlaying() {
playDone = false;
}
public synchronized void stopPlaying() {
playDone = true;
}
public synchronized void add(File file) {
playList.add(file);
}
public void run() {
for (;;) {
if (playList.isEmpty()) {
try {
goToSleep();
} catch (InterruptedException e) {
continue;
}
} else {
playSound(playList.remove(0));
}
}
}
private void sleep(int t) {
try { Thread.sleep(t); } catch (InterruptedException e) {}
}
private void setVolume(SourceDataLine line, int vol) {
try {
FloatControl control = (FloatControl) line
.getControl(FloatControl.Type.MASTER_GAIN);
if (control != null) {
// The gain (dB) and volume (percent) are log related.
// 50% volume = -6dB
// 10% volume = -20dB
// 1% volume = -40dB
// Use max/min for 100,0%.
float gain = (vol <= 0) ? control.getMinimum()
: (vol >= 100) ? control.getMaximum()
: 20.0f * (float) Math.log10(0.01f * vol);
control.setValue(gain);
logger.finest("Using volume " + vol + "%, gain = " + gain);
} else {
logger.warning("No master gain control,"
+ " unable to change the volume.");
}
} catch (Exception e) {
logger.log(Level.WARNING, "Could not set volume", e);
}
}
private SourceDataLine openLine(AudioFormat audioFormat) {
SourceDataLine line = null;
DataLine.Info info = new DataLine.Info(SourceDataLine.class,
audioFormat);
try {
line = (SourceDataLine) mixer.getLine(info);
line.open(audioFormat);
line.start();
setVolume(line, volume);
} catch (Exception e) {
logger.log(Level.WARNING, "Can not open SourceDataLine", e);
}
return line;
}
private boolean playSound(File file) {
BufferedInputStream bis;
try {
bis = new BufferedInputStream(new FileInputStream(file));
bis.mark(1000);
bis.skip(1);
bis.reset();
} catch (FileNotFoundException e) {
logger.warning("Could not find audio file: " + file.getName());
return false;
} catch (IOException e) {
logger.warning("Could not prepare stream for: "
+ file.getName());
return false;
}
AudioInputStream in;
try {
in = AudioSystem.getAudioInputStream(bis);
} catch (Exception e) {
logger.warning("Could not get audio input stream for: "
+ file.getName());
return false;
}
boolean ret = false;
AudioFormat baseFormat = in.getFormat();
AudioFormat decodedFormat
= new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
baseFormat.getSampleRate(),
16,
baseFormat.getChannels(),
baseFormat.getChannels() * (16 / 8),
baseFormat.getSampleRate(),
baseFormat.isBigEndian());
AudioInputStream din
= AudioSystem.getAudioInputStream(decodedFormat, in);
if (din == null) {
logger.warning("Can not get decoded audio input stream");
} else {
SourceDataLine line = openLine(decodedFormat);
if (line != null) {
try {
startPlaying();
rawplay(din, line);
ret = true;
} catch (IOException e) {
logger.log(Level.WARNING, "Error playing: "
+ file.getName(), e);
} finally {
stopPlaying();
line.drain();
line.stop();
line.close();
}
}
}
try {
if (din != null) din.close();
in.close();
} catch (IOException e) {} // Ignore errors on close
return ret;
}
private void rawplay(AudioInputStream din, SourceDataLine lin)
throws IOException {
byte[] data = new byte[8192];
for (;;) {
if (!keepPlaying()) {
break;
}
int read = din.read(data, 0, data.length);
if (read < 0) {
break;
} else if (read > 0) {
lin.write(data, 0, read);
} else {
sleep(50);
}
}
}
}
}